package com.jsy.common.util.gitIncrement;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Maven项目Git增量文件处理工具类
 * 1. 获取Git增量提交文件列表
 * 2. 映射到编译后的class文件
 * 3. 复制并压缩这些文件
 * git log
 * git log --pretty=format:"%h" -n 1
 * git diff --name-only -r baseCommit..targetCommit
 *
 * 备注：
 * 1. 仅支持Maven项目，且项目目录结构为：src/main/java、src/main/resources、src/main/webapp、pom.xml
 * 2. 生成增量包后需要验证包的代码是否和源代码一致
 * 3. 配置文件（线程、properties、yml）,打包时根据提交日志手动配置
 * 4. pom.xml 文件修改涉及到的依赖，需要手动处理，或新增或替换或删除
 * 5. database 数据库文件提交脚本，需要单独列出来
 *
 * @author gates.huang
 * @date 2025/05/18 00:00:01
 */
public class MavenGitDeltaTool {
    private final Path projectRoot;         // 项目根目录
    private final String baseCommit;        // 基准提交点
    private final String targetCommit;      // 目标提交点
    private final Path outputDir;           // 输出目录
    private final Path zipOutputPath;       // 压缩文件输出路径
    private final Path sourceOutputDir;     // 源文件输出目录
    private final Path zipSourceOutputPath;     // 源压缩文件输出目录
    
    // Maven标准目录,需要复制的文件
    private static final String SRC_MAIN_JAVA = "src/main/java/"; //  java源代码
    private static final String SRC_MAIN_RESOURCES = "src/main/resources/";// 资源文件
    private static final String SRC_MAIN_WEBAPP = "src/main/webapp/"; // webapp文件
    private static final String SRC_MAIN_DATABASE  = "core/database/"; // 数据库文件
    private static final String SRC_MAIN_POM_XML  = "pom.xml"; // pom.xml文件
    private static final String TARGET_CLASSES = "target/classes/"; //  编译后的class文件
    private static final String TARGET_WEB_INF_CLASSES = "WEB-INF/classes/"; // 发版包中的类路径文件

    private static HashMap<String, String> compilePathMap = new HashMap<>();

    public MavenGitDeltaTool(Path projectRoot, String baseCommit, String targetCommit, 
                            Path outputDir, Path zipOutputPath, Path sourceOutputDir, Path zipSourceOutputPath) {
        this.projectRoot = projectRoot;
        this.baseCommit = baseCommit;
        this.targetCommit = targetCommit;
        this.outputDir = outputDir;
        this.zipOutputPath = zipOutputPath;
        this.sourceOutputDir = sourceOutputDir;
        this.zipSourceOutputPath = zipSourceOutputPath;

        compilePathMap.put(SRC_MAIN_JAVA, TARGET_CLASSES);
        compilePathMap.put(SRC_MAIN_RESOURCES, TARGET_CLASSES);
        compilePathMap.put(SRC_MAIN_WEBAPP, SRC_MAIN_WEBAPP);
        compilePathMap.put(SRC_MAIN_DATABASE, SRC_MAIN_DATABASE);
        compilePathMap.put(SRC_MAIN_POM_XML, SRC_MAIN_POM_XML);
    }

    // 执行完整流程：获取增量文件 -> 映射编译文件 -> 复制 -> 压缩
    public void execute(boolean isWizbank) throws Exception {
        // 1. 获取Git增量文件
        List<String> changedFiles = getGitChangedFiles();
        System.out.println("检测到 " + changedFiles.size() + " 个文件变更");

        // 2. 生成版本日志
        String readmeContent = generateVersionLog(projectRoot.toString(), baseCommit, targetCommit);

        // 3. 复制文件源文件
        copyChangedFiles(projectRoot.toString(), changedFiles, sourceOutputDir.toString());

        // 4. 生成源码版本日志
        Path readmePath = sourceOutputDir.resolve("readme.txt");
        Files.write(readmePath, readmeContent.getBytes(StandardCharsets.UTF_8));

        // 4. 源码压缩文件
        zipDirectory(sourceOutputDir, zipSourceOutputPath);

        // 5. 映射到编译后的文件路径，用于增量发版
        Map<String, Path> compiledFiles = mapToCompiledFiles(changedFiles);
        System.out.println("找到 " + compiledFiles.size() + " 个编译后的文件");
        
        // 6. 复制文件到输出目录,需手动复制部分目录
        copyFilesToOutputDir(compiledFiles);
        System.out.println("class文件复制完成，输出目录: " + outputDir);

        if (isWizbank){
            // 6. 复制输出到打包目录，修改目录结构，用于wizbank快速发版
            String packageName = "wizbank";
            copyFilesToOutputDirAndAdapter(compiledFiles,outputDir,packageName);
            System.out.println("文件复制完成，输出目录: " + outputDir+File.separator+packageName);
        }

        // 7. 生成发版包版本日志
        Path targetReadmePath = outputDir.resolve("readme.txt");
        Files.write(targetReadmePath, readmeContent.getBytes(StandardCharsets.UTF_8));
        System.out.println("发版包版本日志生成完成，输出目录: " + targetReadmePath);
        
        // 8. 压缩输出目录
        zipDirectory(outputDir, zipOutputPath);
        System.out.println("压缩完成，压缩文件: " + zipOutputPath);

        // 9.输出提醒
        System.out.println("备注：");
        System.out.println("1.wizbank目录为增量发版包，发版前需要手动修改配置文件");
        System.out.println("2.如果有pom.xml文件修改需要单独复制依赖包到WEB-INF/lib目录中");
    }
    
    // 获取Git增量文件列表
    private List<String> getGitChangedFiles() throws IOException, InterruptedException {
        List<String> result = new ArrayList<>();
        
        // 执行git diff命令获取增量文件
        ProcessBuilder pb = new ProcessBuilder(
            "git", "diff", "--name-only", baseCommit, targetCommit
        );
        pb.directory(projectRoot.toFile());
        
        Process process = pb.start();

        
        // 读取命令输出
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                result.add(line.trim());
            }
        }
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("Git命令执行失败，退出码: " + exitCode);
        }
        return result;
    }
    
    // 将源代码文件映射到编译后的文件
    private Map<String, Path> mapToCompiledFiles(List<String> sourceFiles) {
        Map<String, Path> result = new HashMap<>();
        
        for (String sourceFile : sourceFiles) {
            Path sourcePath = projectRoot.resolve(sourceFile);
            String sourceFullPath = sourcePath.toAbsolutePath().toString();
            sourceFullPath = sourceFullPath.replaceAll("\\\\", "/");
            if (sourceFullPath.contains(SRC_MAIN_JAVA)) {
                // Java源代码文件
                String classFile = getCompilePath(sourceFullPath,  SRC_MAIN_JAVA);
                Path classFilePath = Paths.get(classFile);
                result.put(sourceFullPath, classFilePath);
                
                // 检查是否有内部类
                List<Path> innerClassFiles = findInnerClassFiles(classFilePath);
                for (Path innerClassFile : innerClassFiles) {
                    result.put(sourceFullPath + " (inner class)", innerClassFile);
                }
            } else if (sourceFullPath.contains(SRC_MAIN_RESOURCES)) {
                // 资源文件
                String compilePath = getCompilePath(sourceFullPath, SRC_MAIN_RESOURCES);
                Path resourcePath = Paths.get(compilePath);
                result.put(sourceFullPath, resourcePath);
                System.out.println("资源文件："+resourcePath);
            } else if (sourceFullPath.contains(SRC_MAIN_WEBAPP)) {
                // webapp文件
                String compilePath = getCompilePath(sourceFullPath, SRC_MAIN_WEBAPP);
                Path webappPath = Paths.get(compilePath);
                result.put(sourceFullPath, webappPath);
            } else if (sourceFullPath.contains(SRC_MAIN_DATABASE)) {
                // 数据库文件
                String compilePath = getCompilePath(sourceFullPath, SRC_MAIN_DATABASE);
                Path databasePath = Paths.get(compilePath);
                result.put(sourceFullPath, databasePath);
                System.out.println("数据库文件："+databasePath);
            } else if (sourceFullPath.contains(SRC_MAIN_POM_XML)) {
                // pom.xml文件
                String compilePath = getCompilePath(sourceFullPath,  SRC_MAIN_POM_XML);
                Path pomPath = Paths.get(compilePath);
                result.put(sourceFullPath, pomPath);
                System.out.println("pom.xml文件："+pomPath);
            }else {
                System.out.println("未知文件类型: " + sourceFullPath);
            }
        }
        
        return result;
    }

    private String getCompilePath(String sourceFullPath, String srcMain) {
        String compilePath = sourceFullPath;
       if (sourceFullPath!=null){
           compilePath = sourceFullPath.replace(srcMain, compilePathMap.get(srcMain));
           if (sourceFullPath.endsWith(".java")){
               compilePath = compilePath.replace(".java", ".class");
           }
       }
       return compilePath;
    }


    // 查找内部类文件
    private List<Path> findInnerClassFiles(Path classFilePath) {
        List<Path> result = new ArrayList<>();
        Path parentDir = classFilePath.getParent();
        String baseName = classFilePath.getFileName().toString().replace(".class", "");
        
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(parentDir, 
                baseName + "$*.class")) {
            for (Path path : stream) {
                result.add(path);
            }
        } catch (IOException e) {
            // 忽略异常，继续处理其他文件
        }
        
        return result;
    }
    
    // 复制文件到输出目录
    private void copyFilesToOutputDir(Map<String, Path> compiledFiles) throws IOException {
        // 创建输出目录
        Files.createDirectories(outputDir);
        
        for (Map.Entry<String, Path> entry : compiledFiles.entrySet()) {
            Path sourcePath = entry.getValue();
            if (Files.exists(sourcePath)) {
                // 计算目标路径（保持目录结构）构建相对路径
                Path targetPath = outputDir.resolve(
                    projectRoot.relativize(sourcePath).toString());
                
                // 创建目标目录
                Files.createDirectories(targetPath.getParent());
                
                // 复制文件
                Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
//                System.out.println("复制: " + sourcePath + " -> " + targetPath);
            } else {
                System.out.println("警告: 文件不存在 - " + sourcePath);
            }
        }
    }
    
    // 压缩目录
    private void zipDirectory(Path sourceDir, Path zipPath) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath))) {
            Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
                        throws IOException {
                    // 创建ZIP条目并写入文件内容
                    Path relativePath = sourceDir.relativize(file);
                    zos.putNextEntry(new ZipEntry(relativePath.toString()));
                    
                    try (InputStream is = Files.newInputStream(file)) {
                        byte[] buffer = new byte[4096];
                        int bytesRead;
                        while ((bytesRead = is.read(buffer)) != -1) {
                            zos.write(buffer, 0, bytesRead);
                        }
                    }
                    
                    zos.closeEntry();
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }

    /**
     * 复制变更的文件到指定目录
     * @param gitRepoPath Git仓库路径
     * @param changedFiles 变更的文件列表
     * @param outputDir 输出目录
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void copyChangedFiles(String gitRepoPath, List<String> changedFiles, String outputDir) throws IOException {
        for (String file : changedFiles) {
            File sourceFile = new File(gitRepoPath, file);
            File destFile = new File(outputDir, file);

            if (sourceFile.exists()) {
                // 创建目标目录
                destFile.getParentFile().mkdirs();

                // 复制文件
                Files.copy(sourceFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            }
        }
    }

    /**
     * 生成版本日志
     * @param gitRepoPath Git仓库路径
     * @param oldVersion 旧版本号
     * @param newVersion 新版本号
     * @return 版本日志内容
     * @throws IOException 如果操作过程中发生I/O错误
     * @throws InterruptedException 如果操作被中断
     */
    private static String generateVersionLog(String gitRepoPath, String oldVersion, String newVersion)
            throws IOException, InterruptedException {
        StringBuilder log = new StringBuilder();
        log.append("版本变更日志\n");
        log.append("====================\n");
        log.append("生成时间: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n");
        log.append("旧版本: ").append(oldVersion).append("\n");
        log.append("新版本: ").append(newVersion).append("\n\n");
        log.append("变更记录:\n");
        log.append("--------------------\n");

        // 执行git log命令获取提交日志
//        ProcessBuilder pb = new ProcessBuilder("git", "log", "--oneline", oldVersion + ".." + newVersion);
        ProcessBuilder pb = new ProcessBuilder("git", "log", "--format=%B", oldVersion + ".." + newVersion);
        pb.directory(new File(gitRepoPath));
        Process process = pb.start();

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                log.append(line).append("\n");
            }
        }

        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("Git命令执行失败，退出码: " + exitCode);
        }

        return log.toString();
    }

    /**
     * 复制文件到输出目录，并且适配发版结构
     * @param compiledFiles
     * @throws IOException
     */
    private void copyFilesToOutputDirAndAdapter(Map<String, Path> compiledFiles,Path outputDir, String packageName) throws IOException {
        // 创建输出目录
        Path packageOutputDir = outputDir.getParent().resolve(packageName);
        Files.createDirectories(packageOutputDir);

        for (Map.Entry<String, Path> entry : compiledFiles.entrySet()) {
            Path sourcePath = entry.getValue();
            if (Files.exists(sourcePath)) {
                // 计算目标路径（保持目录结构）构建相对路径
                Path targetPath = packageOutputDir.resolve(
                        projectRoot.relativize(sourcePath).toString());
                String packagePath = targetPath.toString().replaceAll("\\\\", "/");
                // 处理发版结构
                if (packagePath.contains(SRC_MAIN_WEBAPP)) {
                    String packageOutputPath = packagePath;
                    String[] split = packagePath.split(SRC_MAIN_WEBAPP);
                    if (split.length >=2){
                        packageOutputPath = split[split.length-1];
                    }
                    targetPath = packageOutputDir.resolve(packageOutputPath);
                } else if (packagePath.contains(TARGET_CLASSES)) {
                    String packageOutputPath = packagePath;
                    String[] split = packagePath.split(TARGET_CLASSES);
                    if (split.length >=2){
                        packageOutputPath = split[split.length-1];
                    }
                    targetPath = packageOutputDir.resolve(TARGET_WEB_INF_CLASSES).resolve(packageOutputPath);
                }
//                System.out.println("targetPath: " + targetPath);
                // 创建目标目录
                Files.createDirectories(targetPath.getParent());

                // 复制文件
                Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
//                System.out.println("复制: " + sourcePath + " -> " + targetPath);
            } else {
                System.out.println("警告: 文件不存在 - " + sourcePath);
            }
        }
    }
    
    // 示例用法
    public static void main(String[] args) {
        try {
            Path projectRoot = Paths.get("F:\\wizbank65\\exeed\\sh_wb_dev");// git项目根目录
            Path sourceDir =  Paths.get("E:\\temp\\source"); // 源文件输出目录
            Path outputDir =  Paths.get("E:\\temp\\release"); // 替换java文件输出目录
            Path zipSourceOutputPath =  Paths.get("E:\\temp\\source.zip"); // 源压缩包文件输出目录
            Path zipOutputPath =  Paths.get("E:\\temp\\release.zip"); // 替换java文件压缩包输出目录
            String baseCommit = "df76605894861c0710da2dad2ee0ece5ccf64e1a"; //  基础版本
            String targetCommit = "197c2f0cca4ad387f69c5695ba2829765e49775f"; // 目标版本
            boolean isWizbank = true; // 是否是wizbank项目
            MavenGitDeltaTool tool = new MavenGitDeltaTool(
                projectRoot, baseCommit, targetCommit, outputDir, zipOutputPath, sourceDir,  zipSourceOutputPath
            );
            System.out.println("开始执行");
            tool.execute(isWizbank);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}